/*
 * Copyright 1999-2101 Alibaba Group.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.fastjson;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.parser.DefaultExtJSONParser;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.JavaBeanMapping;
import com.alibaba.fastjson.parser.SymbolTable;
import com.alibaba.fastjson.parser.TypeUtils;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.visitor.DefaultJSONOutputVisitor;
import com.alibaba.fastjson.visitor.DefaultJavaBeanHandler;
import com.alibaba.fastjson.visitor.JSONOutputVisitor;
import com.alibaba.fastjson.visitor.JSONPrettyFormatOutputVisitor;
import com.alibaba.fastjson.visitor.JavaBeanHandler;

/**
 * 
 * @author wenshao<szujobs@hotmail.com>
 * 
 */
public abstract class JSON implements JSONStreamAware, JSONAware {
	public static final int DEFAULT_PARSER_FEATURE;
	static {
		int features = 0;
		features |= Feature.AutoCloseSource.getMask();
		features |= Feature.InternFieldNames.getMask();
		features |= Feature.UseBigDecimal.getMask();
		features |= Feature.AllowUnQuotedFieldNames.getMask();
		features |= Feature.AllowSingleQuotes.getMask();
		features |= Feature.AllowArbitraryCommas.getMask();
		DEFAULT_PARSER_FEATURE = features;
	}

	public static final int DEFAULT_GENERATE_FEATURE;
	static {
		int features = 0;
		features |= com.alibaba.fastjson.serializer.SerializerFeature.QuoteFieldNames.getMask();
		DEFAULT_GENERATE_FEATURE = features;
	}

	private static final SymbolTable symbolTable = new SymbolTable();

	public static final SymbolTable getGlobalSymbolTable() {
		return symbolTable;
	}

	public static final Object parse(String text) {
		return parse(text, DEFAULT_PARSER_FEATURE);
	}

	public static final Object parse(String text, int features) {
		if (text == null) {
			return null;
		}

		DefaultJSONParser parser = new DefaultJSONParser(text, getGlobalSymbolTable(), features);
		Object value = parser.parse();

		parser.close();

		return value;
	}

	public static final Object parse(String text, Feature... features) {
		int featureValues = DEFAULT_PARSER_FEATURE;
		for (Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		return parse(text, featureValues);
	}

	public static final JSONObject parseObject(String text, Feature... features) {
		return (JSONObject) parse(text, features);
	}

	public static final JSONObject parseObject(String text) {
		return (JSONObject) parse(text);
	}

	public static final <T> T parseObject(String text, Class<T> clazz, Feature... features) {
		if (text == null) {
			return null;
		}

		int featureValues = DEFAULT_PARSER_FEATURE;
		for (Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		DefaultExtJSONParser parser = new DefaultExtJSONParser(text, getGlobalSymbolTable(), featureValues);
		T value = parser.parseObject(clazz);

		if (clazz != JSONArray.class) {
			parser.close();
		}

		return value;
	}

	public static final <T> T parseObject(String text, Class<T> clazz) {
		return parseObject(text, clazz, new Feature[0]);
	}

	public static final JSONArray parseArray(String text) {
		return (JSONArray) parse(text);
	}

	public static final <T> List<T> parseArray(String text, Class<T> clazz) {
		if (text == null) {
			return null;
		}

		List<T> list = new ArrayList<T>();

		DefaultExtJSONParser parser = new DefaultExtJSONParser(text, getGlobalSymbolTable());
		parser.parseArray(clazz, list);

		parser.close();

		return list;
	}

	// ======================

	public static final String toJSONString(Object object) {
		SerializeWriter out = new SerializeWriter();
		try {
			JSONSerializer serializer = new JSONSerializer(out);
			serializer.write(object);
			return out.toString();
		} catch (StackOverflowError e) {
			throw new JSONException("maybe circular references", e);
		} finally {
			out.close();
		}
	}

	public static final String toJSONString(Object object,
			com.alibaba.fastjson.serializer.SerializerFeature... features) {
		SerializeWriter out = new SerializeWriter();

		try {
			JSONSerializer serializer = new JSONSerializer(out);
			for (com.alibaba.fastjson.serializer.SerializerFeature feature : features) {
				serializer.config(feature, true);
			}
			
			serializer.write(object);
			
			return out.toString();
		} catch (StackOverflowError e) {
			throw new JSONException("maybe circular references", e);
		} finally {
			out.close();
		}
	}

	public static final String toJSONString(Object object, boolean prettyFormat) {
		if (!prettyFormat) {
			return toJSONString(object);
		}

		return toJSONString(object, DefaultJavaBeanHandler.getInstance(), prettyFormat);
	}

	public static final String toJSONString(Object object, JavaBeanHandler javaBeanHandler) {
		return toJSONString(object, javaBeanHandler, false);
	}

	public static final String toJSONString(Object object, JavaBeanHandler javaBeanHandler, boolean prettyFormat) {
		SerializeWriter out = new SerializeWriter();

		try {
			JSONOutputVisitor visitor;
	
			if (prettyFormat) {
				visitor = new JSONPrettyFormatOutputVisitor(out);
			} else {
				visitor = new DefaultJSONOutputVisitor(out);
			}
	
			visitor.setJavaBeanHandler(javaBeanHandler);
	
			visitor.accept(object);
	
			return out.toString();
		} finally {
			out.close();
		}
	}

	// ======================================

	@Override
	public String toString() {
		return toJSONString();
	}

	public String toJSONString() {
		SerializeWriter out = new SerializeWriter();
		try {
			new JSONSerializer(out).write(this);
			return out.toString();
		} finally {
			out.close();
		}
	}

	public void writeJSONString(Appendable out) {
		if (out instanceof SerializeWriter) {
			DefaultJSONOutputVisitor visitor = new DefaultJSONOutputVisitor((SerializeWriter) out);
			visitor.accept(this);
		} else {
			SerializeWriter writer = new SerializeWriter();

			try {
				DefaultJSONOutputVisitor visitor = new DefaultJSONOutputVisitor(writer);
				visitor.accept(this);
				
				if (out instanceof Writer) {
					writer.writeTo((Writer) out);
				} else {
					out.append(writer.toString());
				}
			} catch (IOException ex) {
				throw new JSONException(ex.getMessage(), ex);
			} finally {
				writer.close();
			}
		}
	}

	// ///////

	public static final Object toJSON(Object javaObject) {
		return toJSON(javaObject, JavaBeanMapping.getGlobalInstance());
	}

	@SuppressWarnings("unchecked")
	public static final Object toJSON(Object javaObject, JavaBeanMapping mapping) {
		if (javaObject == null) {
			return null;
		}

		if (javaObject instanceof JSON) {
			return (JSON) javaObject;
		}

		if (javaObject instanceof Map) {
			Map<String, Object> map = (Map<String, Object>) javaObject;

			JSONObject json = new JSONObject(map.size());

			for (Map.Entry<String, Object> entry : map.entrySet()) {
				Object jsonValue = toJSON(entry.getValue());
				json.put(entry.getKey(), jsonValue);
			}

			return json;
		}

		if (javaObject instanceof Collection) {
			Collection<Object> collection = (Collection<Object>) javaObject;

			JSONArray array = new JSONArray(collection.size());

			for (Object item : collection) {
				Object jsonValue = toJSON(item);
				array.add(jsonValue);
			}

			return array;
		}

		Class<?> clazz = javaObject.getClass();

		if (clazz.isArray()) {
			int len = Array.getLength(javaObject);

			JSONArray array = new JSONArray(len);

			for (int i = 0; i < len; ++i) {
				Object item = Array.get(javaObject, i);
				Object jsonValue = toJSON(item);
				array.add(jsonValue);
			}

			return array;
		}

		if (mapping.isPrimitive(clazz)) {
			return javaObject;
		}

		try {
			Map<String, Method> getters = mapping.getGetters(clazz);

			JSONObject json = new JSONObject(getters.size());

			for (Map.Entry<String, Method> getter : getters.entrySet()) {
				String key = getter.getKey();
				Method method = getter.getValue();
				Object value = method.invoke(javaObject, new Object[0]);
				Object jsonValue = toJSON(value);

				json.put(key, jsonValue);
			}

			return json;
		} catch (Exception e) {
			throw new JSONException("toJSON error", e);
		}
	}

	public static final <T> T toJavaObject(JSON json, Class<T> clazz) {
		return TypeUtils.cast(json, clazz, JavaBeanMapping.getGlobalInstance());
	}
}
